package cn.mdmbct.douyin.common.proxy;

import cn.mdmbct.douyin.common.executor.ServiceExecutor;
import cn.mdmbct.douyin.common.proxy.annotations.Provider;
import cn.mdmbct.douyin.common.proxy.invoker.DefaultServiceMethodInvoker;
import cn.mdmbct.douyin.common.proxy.invoker.PlainMethodInvoker;
import cn.mdmbct.douyin.common.proxy.invoker.ServiceMethodInvoker;
import cn.mdmbct.douyin.common.proxy.invoker.ServiceMethodWithProviderInvoker;
import cn.mdmbct.douyin.common.utils.MapUtils;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;

/**
 * @author mdmbct  mdmbct@outlook.com
 * @date 2021/5/9 下午3:02
 * @modified mdmbct
 * @since 1.0
 */
public class ServiceProxy<T> implements InvocationHandler {

    private final ServiceExecutor serviceExecutor;

    private final Map<Method, ServiceMethodInvoker<T>> methodCache;

    /**
     * 来自mybatis源码
     * start===>
     */
    private static final int ALLOWED_MODES = MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
            | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC;
    private static final Constructor<MethodHandles.Lookup> lookupConstructor;
    private static final Method privateLookupInMethod;
    // ===>end

    public ServiceProxy(ServiceExecutor serviceExecutor, Map<Method, ServiceMethodInvoker<T>> methodCache) {
        this.serviceExecutor = serviceExecutor;
        this.methodCache = methodCache;
    }

    static {
        Method privateLookupIn;
        try {
            privateLookupIn = MethodHandles.class.getMethod("privateLookupIn", Class.class, MethodHandles.Lookup.class);
        } catch (NoSuchMethodException e) {
            privateLookupIn = null;
        }
        privateLookupInMethod = privateLookupIn;

        Constructor<MethodHandles.Lookup> lookup = null;
        if (privateLookupInMethod == null) {
            // JDK 1.8
            try {
                lookup = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
                lookup.setAccessible(true);
            } catch (NoSuchMethodException e) {
                throw new IllegalStateException(
                        "There is neither 'privateLookupIn(Class, Lookup)' nor 'Lookup(Class, int)' method in java.lang.invoke.MethodHandles.",
                        e);
            } catch (Exception e) {
                lookup = null;
            }
        }
        lookupConstructor = lookup;
    }

    /**
     * @param proxy  代理类。输出其名字可能是`com.sun.proxy.$Proxy0`
     * @param method 被代理方法
     * @param args   被代理方法上的参数数组
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return cachedInvoker(method).invoke(proxy, method, args);
    }


    private ServiceMethodInvoker<T> cachedInvoker(Method method) {

        return MapUtils.computeIfAbsent(methodCache, method, m -> {

            if (m.isDefault()) {
                // default方法
                try {
                    if (privateLookupInMethod == null) {
                        return new DefaultServiceMethodInvoker<T>(getMethodHandleJava8(method));
                    } else {
                        return new DefaultServiceMethodInvoker<T>(getMethodHandleOverJava8(method));
                    }
                } catch (IllegalAccessException | InstantiationException | InvocationTargetException
                        | NoSuchMethodException e) {
                    throw new RuntimeException(e);
                }
            } else if (method.isAnnotationPresent(Provider.class)) {
                //  带有Provider注解的方法
                return new ServiceMethodWithProviderInvoker<T>(serviceExecutor);
            } else {
                // 未实现的方法
                return new PlainMethodInvoker<T>(serviceExecutor);
            }
        });
    }

    /**
     * JDK8
     */
    private MethodHandle getMethodHandleJava8(Method method)
            throws IllegalAccessException, InstantiationException, InvocationTargetException {
        Class<?> declaringClass = method.getDeclaringClass();
        return lookupConstructor.newInstance(declaringClass, ALLOWED_MODES).unreflectSpecial(method, declaringClass);
    }

    /**
     * JDK8以上
     */
    private MethodHandle getMethodHandleOverJava8(Method method)
            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        Class<?> declaringClass = method.getDeclaringClass();
        return ((MethodHandles.Lookup) privateLookupInMethod.invoke(null, declaringClass, MethodHandles.lookup())).findSpecial(
                declaringClass, method.getName(), MethodType.methodType(method.getReturnType(), method.getParameterTypes()),
                declaringClass);
    }
}
